package com.rpp.factory;

import com.rpp.annotation.*;
import com.rpp.utils.AopUtils;
import com.rpp.utils.ClassPathComponentScanner;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Description
 * @Author rpp
 * @Date 2020/6/27 6:56 下午
 */
public class AnnotationApplicationContext {

    private String resource;

    public AnnotationApplicationContext(String resource) {
        this.resource = resource;
    }

    /**
     * 1 解析 component-scan 标签，找出需要扫描的包路径
     * 2 根据包路径找出当前项目目录下被标记注解 @Component @Repository @Service 的class，加载然后创建对象放入容器中
     * 3 遍历被容器管理的bean对应的class，如果类上或者方法上标记@Transactional，生成代理对象
     *
     * @param map
     */
    public void build(Map<String, Object> map) {

        //1 加载配置文件成字节流
        InputStream resourceAsStream = BeanFactory.class.getClassLoader().getResourceAsStream(resource);
        try {
            Document document = new SAXReader().read(resourceAsStream);
            Element rootElement = document.getRootElement();
            //2 解析component-scan标签
            List<Element> list = rootElement.selectNodes("//component-scan");

            if (list == null || list.size() == 0) {
                return;
            }
            if (list.size() > 1) {
                throw new RuntimeException("component-scan 标签只能有一个");
            }
            //3 找出包路径，可以配置多个路径，逗号分隔
            String value = list.get(0).attributeValue("base-package");
            String[] basePackage = value.split(",");

            //4 开始扫描，解析并加载所有的class
            Set<Class<?>> sets = doScan(basePackage);

            //5 根据注解注册实例到容器中
            Set<Class<?>> classSet = registerBean(sets, map);

            //6 设置属性
            setBeanProperty(classSet, map);

            // 7 生成代理类
            createProxy(classSet, map);

        } catch (DocumentException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 扫描包路径，加载所有class
     *
     * @param basePackages
     * @return
     */
    private Set<Class<?>> doScan(String... basePackages) {

        Set<Class<?>> sets = new HashSet<>();
        for (String basePackage : basePackages) {
            //根据包路径加载所有的class
            Set<Class<?>> set = new ClassPathComponentScanner().findCandidateComponents(basePackage);

            sets.addAll(set);
        }
        return sets;
    }

    /**
     * 创建实例 注册bean
     *
     * @param sets
     * @param map
     * @throws Exception
     */
    private Set<Class<?>> registerBean(Set<Class<?>> sets, Map<String, Object> map) throws Exception {
        Set<Class<?>> classSet = new HashSet<>();
        for (Class<?> clss : sets) {
            //是否注解标识 Component Repository Service
            boolean beanFlag = false;
            //获取注解的属性value值 ，设置别名
            String aliasName = null;
            if (clss.isAnnotationPresent(Component.class)) {
                Component annotation = clss.getAnnotation(Component.class);
                beanFlag = true;
                aliasName = annotation.value();
            }
            if (clss.isAnnotationPresent(Repository.class)) {
                Repository annotation = clss.getAnnotation(Repository.class);
                beanFlag = true;
                aliasName = annotation.value();
            }
            if (clss.isAnnotationPresent(Service.class)) {
                Service annotation = clss.getAnnotation(Service.class);
                beanFlag = true;
                aliasName = annotation.value();
            }
            if (beanFlag) {
                Object object = clss.newInstance();
                String name = getName(clss);
                map.put(name, object);
                if (null != aliasName && !aliasName.equals("")) {
                    map.put(aliasName, object);
                }
                classSet.add(clss);
            }
        }
        return classSet;
    }

    /**
     * 将类名首字母转小写,如果实现了接口，取接口的名称
     *
     * @param clss
     * @return
     */
    private String getName(Class<?> clss) {
        String name = clss.getSimpleName();
        Class<?>[] interfaces = clss.getInterfaces();
        if (interfaces.length != 0) {
            name = interfaces[0].getSimpleName();
        }
        char[] chars = name.toCharArray();
        chars[0] += 32;
        return String.valueOf(chars);
    }

    /**
     * 设置bean属性
     *
     * @param classSet
     * @param map
     */
    private void setBeanProperty(Set<Class<?>> classSet, Map<String, Object> map) throws Exception {

        for (Class<?> aClass : classSet) {
            //获取所有字段
            Field[] fields = aClass.getDeclaredFields();
            for (Field field : fields) {
                //如果字段上存在Autowired注解, 从容器中找到此字段对应的实例，并设置字段值
                if (field.isAnnotationPresent(Autowired.class)) {
                    Class<?> fieldClazz = field.getType();
                    field.setAccessible(true);
                    //根据字段名称从容器中获取对应实例
                    Object fieldObj = map.get(getName(fieldClazz));
                    //当前实例
                    Object target = map.get(getName(aClass));
                    field.set(target, fieldObj);
                }
            }
        }
    }

    /**
     * 如果类上或者方法上有@Transactional生成代理类,统一管理事务
     *
     * @param classSet
     * @param map
     * @throws Exception
     */
    private void createProxy(Set<Class<?>> classSet, Map<String, Object> map) {
        for (Class<?> aClass : classSet) {

            //是否需要生成代理类标识
            boolean needCreateProxy = false;
            //问题：即使生成代理类，并不是所有方法都需要增强，需要判断这个方法要不要增强
            //AopUtils 缓存方法与相应注解的关系，用于判断是否增强，key=类名+方法名，value=注解class
            String className = aClass.getName();
            //如果类上标记了注解 Transactional 生成代理类
            boolean classWithAnnotation = aClass.isAnnotationPresent(Transactional.class);
            //如果方法中标记了注解 Transactional 生成代理类
            Method[] methods = aClass.getDeclaredMethods();
            for (Method method : methods) {
                boolean methodWithAnnotation = method.isAnnotationPresent(Transactional.class);
                if (methodWithAnnotation) {
                    needCreateProxy = true;
                }
                if (methodWithAnnotation || classWithAnnotation) {
                    AopUtils.addAnnotation(className + method.getName(), Transactional.class);
                }
            }
            if (needCreateProxy || classWithAnnotation) {
                String name = getName(aClass);
                Object obj = map.get(name);
                //生成代理对象
                Object proxy = ProxyFactory.createProxy(obj);
                //覆盖之前容器中的bean
                map.put(name, proxy);
            }
        }
    }
}
